import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from '../types'
import { parseHeaders } from '../helpers/headers'
import { createAxiosError } from '../helpers/error'
import { isURLSameOrigin } from '../helpers/url'
import cookie from '../helpers/cookie'
import { isFormData } from '../helpers/util'

/**
 * 描述 浏览器端Ajax请求
 * @param {AxiosRequestConfig} config
 * @returns {AxiosPromise}
 */
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  return new Promise((resolve, reject) => {
    const {
      data = null,
      url,
      method = 'get',
      headers,
      responseType,
      timeout,
      cancelToken,
      withCredentials,
      xsrfCookieName,
      xsrfHeaderName,
      onDownloadProgress,
      onUploadProgress,
      auth,
      validateStatus,
    } = config

    const request = new XMLHttpRequest()

    request.open(method.toUpperCase(), url!, true)

    ConfigureRequest()
    processHeaders()
    addEvents()
    processCancel()

    request.send(data)

    /**
     * 描述 辅助函数 处理响应结果
     * @param {AxiosResponse} response 收集好数据的response
     * @returns {void}
     */
    function handleResponse(response: AxiosResponse): void {
      if (!validateStatus || validateStatus!(response.status)) {
        resolve(response)
      } else {
        reject(
          createAxiosError(`Request fail with status code ${response.status}`, {
            config,
            request,
            response,
          })
        )
      }
    }

    /**
     * 描述 对请求之前做配置
     * @returns {void}
     */
    function ConfigureRequest(): void {
      if (responseType) {
        request.responseType = responseType
      }
      if (timeout) {
        request.timeout = timeout
      }
      if (withCredentials) {
        request.withCredentials = withCredentials
      }
      if (data && isFormData(data)) {
        delete headers['Content-Type']
      }
    }

    /**
     * 描述 增加监听事件
     * @returns {void}
     */
    function addEvents(): void {
      if (onUploadProgress) {
        request.upload.onprogress = onUploadProgress
      }
      if (onDownloadProgress) {
        request.onprogress = onDownloadProgress
      }

      request.onreadystatechange = () => {
        if (request.readyState !== 4) {
          return
        }
        if (request.status === 0) {
          return
        }

        const responseHeaders = parseHeaders(request.getAllResponseHeaders())
        const responseData = responseType === 'text' ? request.responseText : request.response
        const response: AxiosResponse = {
          data: responseData,
          status: request.status,
          statusText: request.statusText,
          headers: responseHeaders,
          config,
          request,
        }
        handleResponse(response)
      }

      request.onerror = () => {
        reject(
          createAxiosError('Network Error', {
            config,
            request,
          })
        )
      }

      request.ontimeout = () => {
        reject(
          createAxiosError(`Timeout of ${timeout} ms exceeded`, {
            config,
            code: 'ECONNABORTED',
            request,
          })
        )
      }
    }

    /**
     * 描述 处理取消操作
     * @returns {void}
     */
    function processCancel(): void {
      if (cancelToken) {
        cancelToken.promise.then((reason) => {
          request.abort()
          reject(reason)
        })
      }
    }

    /**
     * 描述 处理请求头
     * @returns {void}
     */
    function processHeaders(): void {
      // 同源的情况下
      if ((withCredentials || isURLSameOrigin(url!)) && xsrfCookieName) {
        const xsrfValue = cookie.read(xsrfCookieName!)
        if (xsrfValue && xsrfHeaderName) {
          headers[xsrfHeaderName] = xsrfValue
        }
      }

      if (auth) {
        headers['Authorization'] = `Basic ${btoa(auth.username + ':' + auth.password)}`
      }

      if (headers) {
        Object.keys(headers).forEach((key) => {
          if (data === null && key.toLowerCase() === 'content-type') {
            delete headers[key]
          } else {
            request.setRequestHeader(key, headers[key])
          }
        })
      }
    }
  })
}
